﻿using System;

namespace Affinity.Synthesis.ExpressionCells
{
    /// <summary>
    /// Represents an expression cell's output value; may be used by other expression cells as an input.
    /// </summary>
    internal class ExpressionCellValue
    {
        /// <summary>
        /// Constructor accepting a reference to the owning expression cell (the expression cell whose output this object will represent).
        /// </summary>
        /// <param name="owningExpressionCell"></param>
        public ExpressionCellValue(IExpressionCell owningExpressionCell)
        {
            OwningExpressionCell = owningExpressionCell;

            /*
            Changed += () => Globals.TraceChannel.SendMessage(new LogMessage("Cell value changed: Owning cell: {0}, Has value: {1}, Value: {2}.", 
                OwningExpressionCell != null ? OwningExpressionCell : (object)"[none]",
                HasValue, 
                HasValue ? Value : "[none]"));
            */
        }

        /// <summary>
        /// Raised when Value changes.
        /// </summary>
        public event Action Changed;

        /// <summary>
        /// Gets whether this ExpressionCellValue has a value. If false, Value should not be used.
        /// </summary>
        public bool HasValue
        {
            get { return _HasValue; }
        }
        
        /// <summary>
        /// Gets the owning expression cell (the expression cell whose output this object represents).
        /// </summary>
        public IExpressionCell OwningExpressionCell { get; private set; }

        /// <summary>
        /// Gets the value of this ExpressionCellValue. This property should not be used when HasValue is false.
        /// </summary>
        public object Value
        {
            get
            {
                if (!HasValue) throw new Exception("Attempt to access undefined value (HasValue is false).");
                return _Value;
            }
        }

        /// <summary>
        /// Clears the event handlers registered with the Changed event.
        /// </summary>
        public void ClearChangedHandlers()
        {
            Changed -= Changed;
        }

        /// <summary>
        /// Raises the Changed event.
        /// </summary>
        public void RaiseChanged()
        {
            if (Changed != null) Changed();
        }

        /// <summary>
        /// Sets whether this ExpressionCellValue has a value and sets the value.
        /// </summary>
        /// <param name="hasValue"></param>
        /// <param name="value"></param>
        public void Set(bool hasValue, object value)
        {
            bool oldHasValue = _HasValue;
            object _oldValue = _Value;

            _HasValue = hasValue;
            if (hasValue) _Value = value;

            if (_HasValue != oldHasValue || !AreValuesEqual(_Value, _oldValue)) RaiseChanged();
        }

        /// <summary>
        /// Determines whether the two values are equal.
        /// </summary>
        /// <param name="first"></param>
        /// <param name="second"></param>
        /// <returns></returns>
        private static bool AreValuesEqual(object first, object second)
        {
            if (first == second) return true;
            if (first != null && !first.GetType().IsByRef) return object.Equals(first, second);
            return false;
        }
        
        private bool _HasValue = false;
        private object _Value = null;
    }
}
